<?php


namespace Module\DataAliyunOss\Support;

use Illuminate\Support\Facades\Log;
use ModStart\Core\Input\InputPackage;
use ModStart\Core\Input\Request;
use ModStart\Core\Input\Response;
use ModStart\Core\Util\PathUtil;
use ModStart\Core\Util\SerializeUtil;
use ModStart\Data\AbstractDataStorage;
use ModStart\Data\Event\DataFileUploadedEvent;
use OSS\Core\OssException;
use OSS\OssClient;

class DataAliyunOssDataStorage extends AbstractDataStorage
{
    /**
     * @var OssClient
     */
    private $client;
    private $bucket;

    public function init()
    {
        $this->client = new OssClient(
            $this->option['accessKeyId'],
            $this->option['accessKeySecret'],
            $this->option['endpoint']
        );
        $this->bucket = $this->option['bucket'];
    }

    public function driverName()
    {
        return 'AliyunOss';
    }


    public function has($file)
    {
        try {
            $ret = $this->client->getObjectMeta($this->bucket, $file);
            if (empty($ret['etag'])) {
                return false;
            }
            return true;
        } catch (\Exception $e) {
            return false;
        }
    }

    public function move($from, $to)
    {
        try {
            $this->client->copyObject($this->bucket, $from, $this->bucket, $to);
        } catch (OssException $e) {
            if (str_contains($e->getMessage(), 'NoSuchKey')) {
            } else {
                throw $e;
            }
        }
        try {
            $this->client->deleteObject($this->bucket, $from);
        } catch (\Exception $e) {
            if (str_contains($e->getMessage(), 'NoSuchKey')) {
            } else {
                throw $e;
            }
        }
    }

    public function delete($file)
    {
        try {
            $ret = $this->client->deleteObject($this->bucket, $file);
        } catch (\Exception $e) {
            if (str_contains($e->getMessage(), 'NoSuchKey')) {
            } else {
                throw $e;
            }
        }
    }

    public function put($file, $content)
    {
        $this->client->putObject($this->bucket, $file, $content);
    }

    public function get($file)
    {
        return $this->client->getObject($this->bucket, $file);
    }

    public function size($file)
    {
        try {
            $ret = $this->client->getObjectMeta($this->bucket, $file);
            return intval($ret['content-length']);
        } catch (\Exception $e) {
            return 0;
        }
    }

    public function multiPartInit($param)
    {
        $token = $this->multiPartInitToken($param);
        $uploadId = $this->client->initiateMultipartUpload($this->bucket, $token['fullPath']);
        if (empty($uploadId)) {
            return Response::generate(-1, 'MultiPartInit init fail');
        }
        $token['multiPartUploadId'] = $uploadId;
        $this->uploadChunkTokenAndUpdateToken($token);
        return Response::generate(0, 'ok', $token);
    }

    public function multiPartUpload($param)
    {
        $token = $this->multiPartInitToken($param);
        $input = $param['input'];
        $category = $param['category'];

        $data = [];
        if (!isset($input['chunks'])) {
            $input['chunks'] = 1;
        }
        if (!isset($input['chunk'])) {
            $input['chunk'] = 0;
        }
        if (empty($input['file'])) {
            return Response::generate(-1, 'MultiPartUpload file empty');
        }
        $data['chunks'] = $input['chunks'];
        $data['chunk'] = $input['chunk'];
        $data['file'] = $input['file'];

        $hash = $token['hash'];
        $hashFile = self::DATA_CHUNK . '/data/' . $hash;
        if ($data['chunk'] < $data['chunks']) {
            // 单个文件上传
            if (!isset($token['multiPartUploadId']) && $data['chunk'] === 0 && $data['chunks'] === 1) {
                $uploadRet = $this->client->uploadFile($this->bucket, $token['fullPath'], $data['file']->getRealPath());
                if (empty($uploadRet['info']['url'])) {
                    return Response::generate(-1, 'MultiPartUpload upload file fail');
                }
                DataFileUploadedEvent::fire('AliyunOss', $category, $token['fullPath']);
                $dataTemp = $this->repository->addTemp($category, $token['path'], $token['name'], $token['size'], empty($token['md5']) ? null : $token['md5']);
                $data['data'] = $dataTemp;
                $data['path'] = $token['fullPath'];
                $data['preview'] = $this->getDriverFullPath($token['fullPath']);
                $data['finished'] = true;
            } else {
                $options = [
                    OssClient::OSS_FILE_UPLOAD => $data['file']->getRealPath(),
                    OssClient::OSS_PART_NUM => $data['chunk'] + 1,
                ];
                try {
                    $eTag = $this->client->uploadPart($this->bucket, $token['fullPath'], $token['multiPartUploadId'], $options);
                } catch (\Exception $e) {
                    Log::info('DataAliyunOssDataStorage.error - ' . $e->getMessage() . ' - ' . $e->getTraceAsString());
                    Log::info('DataAliyunOssDataStorage.token - ' . SerializeUtil::jsonEncode($token));
                    Log::info('DataAliyunOssDataStorage.input - ' . SerializeUtil::jsonEncode(InputPackage::buildFromInput()->all()));
                    throw $e;
                }
                if (empty($eTag)) {
                    return Response::generate(-1, 'MultiPartUpload upload part fail');
                }
                if (empty($token['partIds'])) {
                    $token['partIds'] = [];
                }
                $token['partIds'][] = [
                    'PartNumber' => $data['chunk'] + 1,
                    'ETag' => $eTag,
                    'Size' => filesize($data['file']->getRealPath()),
                ];
                $token['chunkUploaded'] = $data['chunk'] + 1;
                $this->uploadChunkTokenAndUpdateToken($token);
                $data['finished'] = false;
                if ($token['chunkUploaded'] == $data['chunks']) {
                    try {
                        $ret = $this->client->completeMultipartUpload($this->bucket, $token['fullPath'], $token['multiPartUploadId'], $token['partIds']);
                    } catch (\Exception $e) {
                        try {
                            $this->client->abortMultipartUpload($this->bucket, $token['fullPath'], $token['multiPartUploadId']);
                        } catch (\Exception $e) {
                        }
                        $this->uploadChunkTokenAndDeleteToken($token);
                        $message = $e->getMessage();
                        if (str_contains($message, 'InvalidPartOrder')) {
                            return Response::generateError('文件上传失败：文件组合失败，请重试 ShouldRetryUpload');
                        }
                        return Response::generateError('文件上传失败：' . $message . ' ShouldRetryUpload');
                    }
                    $this->uploadChunkTokenAndDeleteToken($token);
                    DataFileUploadedEvent::fire('AliyunOss', $category, $token['fullPath']);
                    if (empty($ret['etag'])) {
                        return Response::generate(-1, 'MultiPartUpload completeMultipartUpload fail ShouldRetryUpload');
                    }
                    $hashFileSize = 0;
                    foreach ($token['partIds'] as $partId) {
                        $hashFileSize += $partId['Size'];
                    }
                    if ($hashFileSize != $token['size']) {
                        return Response::generate(-1, 'MultiPartUpload completeMultipartUpload fail (' . $hashFileSize . ',' . $token['size'] . ') ShouldRetryUpload');
                    }
                    $dataTemp = $this->repository->addTemp($category, $token['path'], $token['name'], $token['size'], empty($token['md5']) ? null : $token['md5']);
                    $data['data'] = $dataTemp;
                    $data['path'] = $token['fullPath'];
                    $data['preview'] = $this->getDriverFullPath($token['fullPath']);
                    $data['finished'] = true;

                }
            }
        }
        return Response::generate(0, 'ok', $data);
    }

    public function domain()
    {
        return modstart_config()->getWithEnv('DataAliyunOss_Domain');
    }

    public function domainInternal()
    {
        return modstart_config()->getWithEnv('DataAliyunOss_DomainInternal', modstart_config()->getWithEnv('DataAliyunOss_Domain'));
    }


    public function updateDriverDomain($data)
    {
        $update = [
            'driver' => 'AliyunOss',
            'domain' => $this->domain(),
        ];
        $this->repository->updateData($data['id'], $update);
        return array_merge($data, $update);
    }

    public function getDriverFullPath($path)
    {
        $path = parent::getDriverFullPath($path);
        if (PathUtil::isPublicNetPath($path)) {
            return $path;
        }
        return $this->domain() . $path;
    }

    /**
     * @param $path
     * @return mixed|string
     * @deprecated delete at 2024-04-25
     */
    public function getDriverFullPathInternal($path)
    {
        $path = parent::getDriverFullPathInternal($path);
        if (PathUtil::isPublicNetPath($path)) {
            return $path;
        }
        return $this->domainInternal() . $path;
    }


}
